#include "baseserialport.h"
#include <QDebug>
#include "protocol_t20.h"
#include "crc.h"
#include <QSerialPort>     //提供访问串口的功能
#include <QSerialPortInfo> //提供系统中存在的串口的信息
#include <QMetaEnum>

const int BaseSerialport::MAX_BUFFER_SIZE = 4096;

BaseSerialport::BaseSerialport(
    QObject *parent)
    : QObject{parent}
{}

void BaseSerialport::open(
    const QString &portName,
    QSerialPort::BaudRate baudRate,
    QSerialPort::DataBits dataBits,
    QSerialPort::Parity parity,
    QSerialPort::StopBits stopBits)
{
    if (serialPort != nullptr)
    {
        this->close();
    }

    serialPort = new QSerialPort(portName);

    connect(serialPort, &QSerialPort::readyRead, this, &BaseSerialport::onReadyRead);
    connect(serialPort, &QSerialPort::errorOccurred, this, &BaseSerialport::onErrorOccurred);

    if (!serialPort->open(QIODevice::ReadWrite))
    {
        qDebug() << QString("open serialport %1 failed !").arg(portName);
        emit openResult(false);
        return;
    }

    qDebug() << QString("open serialport %1 success !").arg(portName);

    serialPort->setBaudRate(baudRate);
    serialPort->setDataBits(dataBits);
    serialPort->setParity(parity);
    serialPort->setStopBits(stopBits);

    emit openResult(true);
}

bool BaseSerialport::send(
    QByteArray data)
{
    if (serialPort == nullptr)
    {
        qDebug() << "serialport is not open!";
        return false;
    }

    qDebug() << QString("size:%1, data:%2, hex:%3")
                    .arg(serialPort->write(data))
                    .arg(QString(data))
                    .arg(QString(data.toHex()));
    return true;
}

void BaseSerialport::onReadyRead()
{
    if (serialPort == nullptr)
        return;

    // 控制缓冲区大小
    QByteArray data = serialPort->readAll();
    if (bufferByteArray.length() > MAX_BUFFER_SIZE)
        bufferByteArray.clear();

    bufferByteArray.append(data);

    // 是否存在未解析数据
    bool isOk = false;
    // 存储解析出来的完整包
    QByteArray packetData;

    // 不保证缓冲区数据只有一个包，可能会存在粘包，所以进行循环，至少执行一次
    do
    {
        packetData.clear();

        // 解析缓冲区数据，传入空数组用于传递信息
        isOk = parsePacket(packetData);

        // 没有可用完整包，退出循环
        if (packetData.isNull())
            break;

        // 存在完整包，发送给接收信号的函数
        emit sendData(packetData);

    } while (isOk);
}

void BaseSerialport::onErrorOccurred(
    QSerialPort::SerialPortError error)
{
    QMetaEnum meta = QMetaEnum::fromType<QSerialPort::SerialPortError>();
    qDebug() << meta.valueToKey(error);
}

void BaseSerialport::close()
{
    if (serialPort != nullptr)
    {
        serialPort->close();
        serialPort->deleteLater();
        serialPort = nullptr;
    }
    qDebug() << "close serialport";
}

bool BaseSerialport::parsePacket(
    QByteArray &packetData)
{
    // 不存在包头
    if (bufferByteArray.indexOf(Protocol::HeadUp) == -1)
        return false;

    // 存在包头，去掉前面多余数据
    bufferByteArray.remove(0, bufferByteArray.indexOf(Protocol::HeadUp));

    // 数据太短，只有一个包头
    if (bufferByteArray.length() < Protocol::HeadLen + 1)
        return false;

    // 取出【数据长度】字节内容=有效数据长度
    int packetDataLen = bufferByteArray[Protocol::HeadLen];

    // 整包长度
    int totalPacketLen = Protocol::HeadLen + Protocol::LenBit + packetDataLen + Protocol::CheckLen;

    // 缓冲区剩余长度 是否满足 大于解析整包长度
    if (bufferByteArray.length() < totalPacketLen)
        return false;

    // 足够解析出完整一包数据
    // 取出校验位
    uint16_t checkVal = (bufferByteArray[totalPacketLen - 2] << 8) +
                        (bufferByteArray[totalPacketLen - 1] & 0x00ff);

    // 计算当前缓冲区的校验值
    QByteArray checkData = bufferByteArray.mid(Protocol::HeadLen + 1, packetDataLen);
    uint16_t calcCheckVal = modbus_crc16((uint8_t *) checkData.data(), packetDataLen);

    // 校验位相等
    if (calcCheckVal == checkVal)
    {
        // 有效数据
        // 取出完整包
        packetData.append(bufferByteArray.mid(0, totalPacketLen - Protocol::CheckLen));

        // 清除缓冲区中当前包
        bufferByteArray.remove(0, totalPacketLen);
    }
    else
    {
        // 无效数据
        // 清除包头
        bufferByteArray.remove(0, Protocol::HeadLen);
    }
    // 继续搜索，是否存在粘包
    if (bufferByteArray.indexOf(Protocol::HeadUp) == -1)
        return false;

    return true;
}
